package container.queue;

import lombok.AllArgsConstructor;

import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

/**
 * DelayQueue 中的元素只有当其指定的延迟时间到了，才能够从队列中获取到该元素。DelayQueue 是一个没有大小限制的队列，因此往队列中插入数据
 * 的操作（生产者）永远不会被阻塞，而只有获取数据的操作（消费者）才会被阻塞。
 * <p>
 * DelayQueue 阻塞队列在系统开发中也常常会用到，例如：
 * 缓存系统的设计，缓存中的对象，超过了空闲时间，需要从缓存中移出。
 * 任务调度系统，能够准确的把握任务的执行时间。我们可能需要通过线程处理很多时间上要求很严格的数据，如果使用普通的线程，我们就需要遍历所有的对象，一个
 * 一个的检 查看数据是否过期等，首先这样在执行上的效率不会太高，其次就是这种设计的风格也大大的影响了数据的精度。一个需要 12:00 点执行的任务
 * 可能 12:01 才执行，这样对数据要求很高的系统有更大的弊端。由此我们可以使用 DelayQueue。
 * <p>
 * 使用场景：DelayQueue 使用场景较少，但都相当巧妙，常见的例子比如使用一个 DelayQueue 来管理一个超时未响应的连接队列。
 * <p>
 * 使用：为了具有调用行为，存放到 DelayDeque 的元素必须继承 Delayed 接口。Delayed 接口使对象成为延迟对象，它使存放在 DelayQueue 类中的对象
 * 具有了激活日期。该接口强制执行下列两个方法：
 * * CompareTo(Delayed o)：Delayed接口继承了Comparable接口，因此有了这个方法。
 * * getDelay(TimeUnit unit):这个方法返回到激活日期的剩余时间，时间单位由单位参数指定。
 */
public class T05_01_DelayQueue {

    /**
     * 业务场景一：多考生考试
     * 模拟一个考试的日子，考试时间为 120 分钟，30 分钟后才可交卷，当时间到了，或者学生都交完卷了宣布考试结束。
     * 实现思想：用 DelayQueue存储考生（Student 类），每一个考生都有自己的名字和完成试卷的时间，Teacher 线程对 DelayQueue 进行监控，
     * 收取完成试卷小于 120 分钟的学生的试卷。当考试时间 120 分钟到时，先关闭 Teacher 线程，然后强制 DelayQueue 中还存在的考生交卷。
     * 每一个考生交卷都会进行一次
     */
    public static void main(String[] args) throws InterruptedException {
        int studentNumber = 20;
        CountDownLatch countDownLatch = new CountDownLatch(studentNumber + 1);
        DelayQueue<Student> students = new DelayQueue<>();
        Random random = new Random();
        for (int i = 0; i < studentNumber; i++) {
            students.put(new Student("student" + (i + 1), random.nextInt(120) + 30, countDownLatch));
        }

        // 老师监考，准备一个老师即可，把学生们需要交给老师，以便监控
        Thread teacherThread = new Thread(new Teacher(students));

        // 强制交卷，时间为 120 分钟，并且把老师线程穿进去收卷纸
        students.put(new EndExam(students, 120, countDownLatch, teacherThread));
        teacherThread.start();

        countDownLatch.await();
        System.out.println("考试时间到，全部交卷！");
    }

    static class Student implements Runnable, Delayed {
        private final String name;
        private final long workTime; // 希望用时，有的学生小于 120 分钟，有的会大于 120 分钟，会被强制交卷
        private final long submitTime; // 交卷的时间
        private boolean isForce = false;
        private final CountDownLatch countDownLatch;

        public Student(String name, long workTime, CountDownLatch countDownLatch) {
            this.name = name;
            this.workTime = workTime;
            this.submitTime = TimeUnit.NANOSECONDS.convert(workTime, TimeUnit.NANOSECONDS) + System.nanoTime();
            this.countDownLatch = countDownLatch;
        }

        @Override
        public int compareTo(Delayed o) {
            if (!(o instanceof Student))
                return 1;
            if (o == this)
                return 0;
            Student s = (Student) o;
            return Long.compare(this.workTime, s.workTime);
        }

        @Override
        public long getDelay(TimeUnit unit) {
            return unit.convert(submitTime - System.nanoTime(), TimeUnit.NANOSECONDS);
        }

        @Override
        public void run() {
            if (isForce) {
                System.out.println(name + " 交卷, 希望用时" + workTime + "分钟" + " ,实际用时 120分钟");
            } else {
                System.out.println(name + " 交卷, 希望用时" + workTime + "分钟" + " ,实际用时 " + workTime + " 分钟");
            }
            countDownLatch.countDown(); //交卷一个 减一个人
        }

        public void setForce(boolean isForce) {
            this.isForce = isForce;
        }
    }

    static class EndExam extends Student {
        private final DelayQueue<Student> students;
        private final CountDownLatch countDownLatch;
        private final Thread teacherThread;

        public EndExam(DelayQueue<Student> students, long workTime, CountDownLatch countDownLatch, Thread teacherThread) {
            super("强制收卷", workTime, countDownLatch);
            this.students = students;
            this.countDownLatch = countDownLatch;
            this.teacherThread = teacherThread;
        }

        @Override
        public void run() {
            teacherThread.interrupt(); // 打断线程，强制交卷，不要让学生自己 take 了，也可采用一个全局变量标记
            Student tmpStudent;

            for (Student student : students) { // 遍历所有还未执行的学生们，把他们拿出来，手动调用他们的 run 方法交卷
                tmpStudent = student;
                tmpStudent.setForce(true);
                tmpStudent.run();
            }
            countDownLatch.countDown(); // 最后注意，把自己强制交卷的线程，也要一下
        }
    }

    @AllArgsConstructor
    static class Teacher implements Runnable {
        private final DelayQueue<Student> students; // 老师需要知道自己监控哪些学生，宣布考试开始

        @Override
        public void run() {
            try {
                System.out.println("考试开始！");
                // 宣布考试后，才能让学生开始 run
                // 此处需要注意，take 是阻塞的，只要时间到了，才会 take 出来，才会执行 run 方法
                // 中途有可能线程会被 interrupted（比如强制交卷的情况下，就不能再让 take 了，需要执行强制交卷的线程任务）
                while (!Thread.interrupted()) {
                    students.take().run();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
